package com.xhj.mybatis.builder;

import com.xhj.mybatis.cache.Cache;
import com.xhj.mybatis.cache.decorators.LruCache;
import com.xhj.mybatis.cache.impl.PerpetualCache;
import com.xhj.mybatis.executor.keygen.KeyGenerator;
import com.xhj.mybatis.mapping.*;
import com.xhj.mybatis.reflection.MetaClass;
import com.xhj.mybatis.scripting.LanguageDriver;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.type.JdbcType;
import com.xhj.mybatis.type.TypeHandler;

import java.util.*;

import static javassist.util.proxy.ProxyFactory.useCache;

/**
 * MapperBuilderAssistant
 *
 * @author XJks
 * @description 辅助类，主要用于在解析 XxxMapper.xml 映射文件时，协助构建和注册各种映射对象（如 MappedStatement、ResultMap等）到全局配置 Configuration 中。它负责命名空间处理、参数和结果映射的组装等，是连接 XML 配置与运行时元数据的关键桥梁。
 */
public class MapperBuilderAssistant extends BaseBuilder {

    /**
     * 资源路径
     */
    private final String resource;

    /**
     * 当前命名空间
     */
    private String currentNamespace;

    /**
     * 当前使用的缓存对象
     */
    private Cache currentCache;

    public MapperBuilderAssistant(Configuration configuration, String resource) {
        super(configuration);
        this.resource = resource;
    }

    public String getCurrentNamespace() {
        return currentNamespace;
    }

    public void setCurrentNamespace(String currentNamespace) {
        // 如果传入的命名空间为 null，抛出异常，提示必须指定 namespace 属性
        if (currentNamespace == null) {
            throw new RuntimeException("The mapper element requires a namespace attribute to be specified.");
        }
        // 如果当前已经有命名空间，并且和传入的不一致，抛出异常，防止重复或错误设置
        if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
            throw new RuntimeException("Wrong namespace. Expected '"
                    + this.currentNamespace + "' but found '" + currentNamespace + "'.");
        }
        // 设置当前命名空间
        this.currentNamespace = currentNamespace;
    }

    /**
     * 为给定的名称加上当前命名空间前缀，确保唯一性。
     *
     * @param base        原始名称（如 id）
     * @param isReference 是否为引用（true 表示引用其他命名空间的对象）
     * @return 加上命名空间前缀后的名称
     */
    public String applyCurrentNamespace(String base, boolean isReference) {
        // 如果 base 为空，直接返回 null
        if (base == null) {
            return null;
        }
        if (isReference) {
            // 如果是引用，判断是否已经带有命名空间（即包含点号 .）
            if (base.contains(".")) {
                // 已经有命名空间，直接返回
                return base;
            }
        } else {
            // 如果不是引用，判断是否已经以当前命名空间开头
            if (base.startsWith(currentNamespace + ".")) {
                // 已经有命名空间前缀，直接返回
                return base;
            }
            // 如果包含点号，说明有其他命名空间，抛出异常（不允许有点号）
            if (base.contains(".")) {
                throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
            }
        }
        // 给 base 加上当前命名空间前缀，返回
        return currentNamespace + "." + base;
    }

    /**
     * 创建并使用一个新的二级缓存。
     *
     * @param typeClass     缓存的实现类（一般为 PerpetualCache.class）
     * @param evictionClass 缓存的淘汰策略装饰器类（一般为 LruCache.class）
     * @param flushInterval 缓存刷新间隔（毫秒），可为 null 表示不定时刷新
     * @param size          缓存大小上限，超出后按淘汰策略移除
     * @param readWrite     是否为读写缓存（true 表示支持序列化，false 表示只读）
     * @param blocking      是否阻塞，防止缓存击穿
     * @param props         额外的配置信息
     * @return 创建好的 Cache 实例
     */
    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties props) {
        // 创建 CacheBuilder，指定命名空间作为缓存的唯一标识
        Cache cache = new CacheBuilder(currentNamespace)
                // 设置缓存实现类，默认为 PerpetualCache
                .implementation(valueOrDefault(typeClass, PerpetualCache.class))
                // 设置缓存淘汰策略装饰器，默认为 LruCache
                .addDecorator(valueOrDefault(evictionClass, LruCache.class))
                // 设置刷新间隔
                .clearInterval(flushInterval)
                // 设置缓存大小
                .size(size)
                // 设置是否为读写缓存
                .readWrite(readWrite)
                // 设置是否阻塞
                .blocking(blocking)
                // 设置额外属性
                .properties(props)
                // 构建 Cache 实例
                .build();
        // 将新建的缓存添加到全局配置中
        configuration.addCache(cache);
        // 记录当前使用的缓存
        currentCache = cache;
        // 返回新建的缓存对象
        return cache;
    }

    /**
     * 添加一个新的 MappedStatement（映射的 SQL 语句）。
     *
     * @param id             SQL 语句的唯一标识
     * @param sqlSource      SQL 源对象（包含 SQL 语句和参数信息）
     * @param sqlCommandType SQL 命令类型（如 SELECT、INSERT、UPDATE、DELETE）
     * @param parameterType  参数类型，todo:目前未使用
     * @param resultMap      结果映射 id
     * @param resultType     结果类型
     * @param flushCache     是否刷新缓存
     * @param keyGenerator   主键生成器
     * @param keyProperty    主键属性
     * @param keyColumn      主键列
     * @param databaseId     数据库标识符
     * @param lang           语言驱动
     * @return 新建的 MappedStatement 对象
     */
    public MappedStatement addMappedStatement(
            String id,
            SqlSource sqlSource,
            SqlCommandType sqlCommandType,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            boolean flushCache,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn,
            String databaseId,
            LanguageDriver lang) {

        // 应用命名空间前缀到 id 上，生成完整的 MappedStatement ID
        id = applyCurrentNamespace(id, false);
        // 判断当前 SQL 是否为 SELECT 语句
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

        // 创建 MappedStatement 的构建器，设置各种属性
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
                .resource(resource)
                .keyGenerator(keyGenerator)
                .keyProperty(keyProperty)
                .keyColumn(keyColumn)
                .databaseId(databaseId)
                .lang(lang)
                .resultMaps(getStatementResultMaps(resultMap, resultType, id))
                .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
                .useCache(valueOrDefault(useCache, isSelect))
                .cache(currentCache);

        // 构建最终的 MappedStatement 对象
        MappedStatement statement = statementBuilder.build();
        // 将新建的 MappedStatement 注册到全局配置中
        configuration.addMappedStatement(statement);
        // 返回新建的 MappedStatement
        return statement;
    }

    /**
     * 泛型方法：如果传入的 value 为 null，则返回默认值 defaultValue，否则返回 value 本身。
     * 这个方法常用于参数设置，保证不会出现 null 值。
     *
     * @param value        要设置的值
     * @param defaultValue 默认值
     * @param <T>          泛型类型
     * @return 最终的值
     */
    private <T> T valueOrDefault(T value, T defaultValue) {
        // 判断 value 是否为 null，如果是，则返回 defaultValue，否则返回 value
        return value == null ? defaultValue : value;
    }


    /**
     * 获取 SQL 语句对应的 ResultMap 列表（结果映射）。
     * 如果指定了 resultMap，则从配置中查找对应的 ResultMap；
     * 如果没有指定 resultMap，但有 resultType，则创建一个内联的 ResultMap。
     *
     * @param resultMap   结果映射 id（可以为多个，用逗号分隔）
     * @param resultType  结果类型（Java 类），用于没有指定 resultMap 时
     * @param statementId 当前 SQL 语句的唯一标识
     * @return ResultMap 列表
     */
    private List<ResultMap> getStatementResultMaps(
            String resultMap,
            Class<?> resultType,
            String statementId) {

        // 给 resultMap 加上命名空间前缀，确保唯一性
        resultMap = applyCurrentNamespace(resultMap, true);

        // 创建一个空的 ResultMap 列表，用于存放查找到或新建的 ResultMap
        List<ResultMap> resultMaps = new ArrayList<>();

        // 如果指定了 resultMap（不为 null）
        if (resultMap != null) {
            // 按逗号分割，支持多个 resultMap
            String[] resultMapNames = resultMap.split(",");
            // 遍历每个 resultMap 名称
            for (String resultMapName : resultMapNames) {
                try {
                    // 从全局配置中查找对应的 ResultMap，并添加到列表
                    resultMaps.add(configuration.getResultMap(resultMapName.trim()));
                } catch (IllegalArgumentException e) {
                    // 如果找不到对应的 ResultMap，抛出异常，提示引用错误
                    throw new RuntimeException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
                }
            }
        } else if (resultType != null) {
            // 如果没有指定 resultMap，但有 resultType，则创建一个内联的 ResultMap
            ResultMap inlineResultMap = new ResultMap.Builder(
                    configuration,                // MyBatis 全局配置
                    statementId + "-Inline",      // 内联 ResultMap 的唯一 id
                    resultType,                   // 结果类型（Java 类）
                    new ArrayList<>()             // 字段映射列表（此处为空）
            ).build();
            // 将新建的内联 ResultMap 添加到列表
            resultMaps.add(inlineResultMap);
        }
        // 返回最终的 ResultMap 列表
        return resultMaps;
    }

    /**
     * 构建一个 ResultMapping 对象，用于描述数据库字段与 Java 属性的映射关系。
     *
     * @param resultType    结果类型（Java 类）
     * @param property      Java 属性名
     * @param column        数据库字段名
     * @param javaType      Java 类型（可选，优先级高于 resultType）
     * @param jdbcType      JDBC 类型（如 VARCHAR、INTEGER）
     * @param notNullColumn 非空字段名（可选，多个用逗号分隔）
     * @param columnPrefix  字段前缀（可选）
     * @param typeHandler   类型处理器（用于自定义类型转换）
     * @param flags         结果标志（如 CONSTRUCTOR、ID 等）
     * @return 构建好的 ResultMapping 对象
     */
    public ResultMapping buildResultMapping(
            Class<?> resultType, // 结果类型（Java 类）
            String property,     // Java 属性名
            String column,       // 数据库字段名
            Class<?> javaType,   // Java 类型（可选）
            JdbcType jdbcType,   // JDBC 类型
            String notNullColumn,   // 非空字段名（可选）
            String columnPrefix,    // 字段前缀（可选）
            Class<? extends TypeHandler<?>> typeHandler, // 类型处理器
            List<ResultFlag> flags // 结果标志
    ) {
        // 1. 解析 Java 类型，如果未指定 javaType，则根据 resultType 和 property 自动推断
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);

        // 2. 获取类型处理器实例，用于类型转换
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

        // 4. 构建 ResultMapping 对象，设置各项属性
        return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
                .jdbcType(jdbcType) // 设置 JDBC 类型
                .typeHandler(typeHandlerInstance) // 设置类型处理器
                .flags(flags == null ? new ArrayList<>() : flags) // 设置结果标志，默认空列表
                .notNullColumns(parseMultipleColumnNames(notNullColumn)) // 解析并设置非空字段名
                .columnPrefix(columnPrefix) // 设置字段前缀
                .build(); // 构建最终的 ResultMapping 对象
    }

    /**
     * 添加一个新的 ResultMap（结果映射）。
     *
     * @param id             ResultMap 的唯一标识
     * @param type           结果类型（Java 类）
     * @param resultMappings 字段与属性的映射关系列表
     * @param autoMapping    是否自动映射未知列
     * @return 新创建的 ResultMap 对象
     */
    public ResultMap addResultMap(
            String id,
            Class<?> type,
            List<ResultMapping> resultMappings,
            Boolean autoMapping) {
        // 为 id 添加当前命名空间前缀，确保唯一性
        id = applyCurrentNamespace(id, false);
        // 创建新的 ResultMap 对象
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping).build();
        // 将新建的 ResultMap 注册到全局配置中
        configuration.addResultMap(resultMap);
        // 返回新建的 ResultMap
        return resultMap;
    }

    /**
     * 解析多个列名（如 "col1,col2"），返回一个包含所有列名的 Set 集合。
     * 支持单个列名和逗号分隔的多个列名。
     *
     * @param columnName 列名字符串，可以是单个列名或多个列名（用逗号分隔）
     * @return 包含所有列名的 Set 集合
     */
    private Set<String> parseMultipleColumnNames(String columnName) {
        // 创建一个空的 HashSet，用于存放解析出来的列名，Set 保证不重复
        Set<String> columns = new HashSet<>();
        // 如果传入的列名字符串不为 null，才进行解析
        if (columnName != null) {
            // 如果列名字符串中包含逗号，说明有多个列名
            if (columnName.indexOf(',') > -1) {
                // 使用 StringTokenizer 按照 "{}, " 这些字符分割字符串，这样可以支持形如 "{col1,col2}" 或 "col1, col2" 的写法
                StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
                // 只要还有下一个分割出来的片段（即列名），就继续循环
                while (parser.hasMoreTokens()) {
                    // 取出下一个列名
                    String column = parser.nextToken();
                    // 把列名加入 Set 集合
                    columns.add(column);
                }
            } else {
                // 如果没有逗号，说明只有一个列名，直接加入 Set 集合
                columns.add(columnName);
            }
        }
        // 返回最终解析出来的所有列名
        return columns;
    }

    /**
     * 根据给定的 resultType（结果类型）、property（属性名）和 javaType（Java 类型）推断最终的 Java 类型
     *
     * @param resultType 结果类型
     * @param property   属性名
     * @param javaType   Java 类型
     * @return 最终确定的 Java 类型
     */
    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
        // 如果没有显式指定 javaType，并且属性名不为空
        if (javaType == null && property != null) {
            try {
                // 通过 MyBatis 的 MetaClass 工具，获取 resultType 的元数据信息
                MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
                // 尝试获取指定属性的 setter 方法参数类型（即属性的类型），赋值给 javaType
                javaType = metaResultType.getSetterType(property);
            } catch (Exception e) {
                // 如果获取类型时出错（如属性不存在），忽略异常，继续后续判断
                // 后面的 null 检查会处理这种情况
            }
        }
        // 如果 javaType 仍然为 null（即无法推断类型）
        if (javaType == null) {
            // 默认使用 Object.class，表示通用类型
            javaType = Object.class;
        }
        // 返回最终确定的 Java 类型
        return javaType;
    }

}
